package com.iplant.aps.serviceImpl.utils.aps;

import com.iplant.aps.service.apsenum.aps.APSMsgTypes;
import com.iplant.aps.service.apsenum.aps.APSShiftPeriod;
import com.iplant.aps.service.apsenum.aps.APSTaskStatus;
import com.iplant.aps.service.apsenum.oms.OMSOrderStatus;
import com.iplant.aps.service.po.aps.*;
import com.iplant.aps.service.po.oms.OMSOrder;
import com.iplant.aps.service.po.oms.OMSOutsourceOrder;
import com.iplant.aps.service.po.sch.APSMarginCapacityAndResult;
import com.iplant.aps.service.po.sch.APSMarginEntityCapacity;
import com.iplant.aps.service.po.sch.APSMessage;
import com.iplant.aps.serviceImpl.dao.APSTaskPartDAO;
import com.iplant.aps.serviceImpl.dao.APSTaskPartPointDAO;
import com.iplant.base.po.OutResult;
import com.iplant.base.utils.cache.CacheUtils;
import com.iplant.base.utils.general.CalendarTool;
import com.iplant.base.utils.general.StringUtils;
import com.iplant.basic.service.po.bms.BMSEmployee;
import com.iplant.basic.service.po.cfg.CFGCalendar;
import com.iplant.basic.service.po.fmc.*;
import com.iplant.basic.serviceImpl.dao.fmc.FMCUnavailableDAO;
import com.iplant.pdm.service.basicenum.fpc.FPCRouteType;
import com.iplant.pdm.service.po.bop.FPCRoute;
import com.iplant.pdm.service.po.bop.FPCRoutePart;
import com.iplant.pdm.service.po.bop.FPCRoutePartPoint;
import com.iplant.pdm.service.po.fpc.FPCProduct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 自动排程工具类
 *
 * @author Femi.Yang
 */
public class APSAutoUtils {
    private static final Logger logger = LoggerFactory.getLogger(APSAutoUtils.class);

    public APSAutoUtils() {
    }


    /**
     * 默认推演后续天数 避免排程周期长 后序无法排程导致整体计划无法排程
     */
    private static int DefaultDelayDays = 30;

    private static APSAutoUtils Instance;

    public static APSAutoUtils getInstance() {
        if (Instance == null)
            Instance = new APSAutoUtils();
        return Instance;
    }

    /**
     * 工艺排程
     *
     * @param wLoginUser
     * @param wFPCRouteType       工艺类型
     * @param wOrderList          排程订单列表
     * @param wAPSShiftPeriod     排程类型 周/月  日排程使用周计划下达生成APSTaskStation与派工生成主要生产APSTaskSelf解决
     * @param wStartDate          排程开始日期
     * @param wEndDate            排程结束日期
     * @param wCheckMaterial      是否强制检查物料
     * @param wAllowTechDelayHour 是否允许工艺超期,超期时长多少 单位小时
     * @param wAPSMessage
     * @return
     */
    public List<APSTaskPart> APS_AutoTaskPart(BMSEmployee wLoginUser, FPCRouteType wFPCRouteType, List<OMSOrder> wOrderList,
                                              APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate, Calendar wEndDate, boolean wCheckMaterial,
                                              int wAllowTechDelayHour, List<APSMessage> wAPSMessage) {
        List<APSTaskPart> wResult = new ArrayList<>();
        if (wOrderList == null || wOrderList.size() == 0)
            return wResult;

        wOrderList.removeIf(p -> p == null || p.ID <= 0);

        if (wOrderList.size() == 0)
            return wResult;
        OutResult<Integer> wErrorCode = new OutResult<>(0);


        Map<Integer, FPCRoute> wOrderRouteMap = new HashMap<>();
        Map<Integer, FPCProduct> wFPCProductMap = new HashMap<>();
        List<Integer> wEntityIDList = new ArrayList<>();

        List<Integer> wMaterilIDList = new ArrayList<>();
        //检查工艺完整性
        iPlantScheduleUtils.getInstance().CheckOrderRoute(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRouteType, wEntityIDList, wMaterilIDList, wOrderRouteMap, wFPCProductMap, wAPSMessage);
        if (wOrderList.size() == 0) {
            return wResult;
        }
        wEntityIDList = wEntityIDList.stream().distinct().collect(Collectors.toList());
        wEntityIDList.removeIf(p -> p <= 0);

        wMaterilIDList = wMaterilIDList.stream().distinct().collect(Collectors.toList());
        wMaterilIDList.removeIf(p -> p <= 0);


        Map<Integer, List<List<FPCRoutePart>>> wMarginRoutePartListMap = new HashMap<Integer, List<List<FPCRoutePart>>>();
        Map<Integer, List<FPCRoutePart>> wFPCRoutePartListMap = new HashMap<>();
        Map<Integer, List<APSTaskPart>> wAPSTaskPartDoneListMap = new HashMap<>();


        Map<Integer, FPCRoutePart> wRoutePartMap = new HashMap<>();

        for (Integer wOrderID : wOrderRouteMap.keySet()) {
            if (wOrderRouteMap.get(wOrderID) == null || wOrderRouteMap.get(wOrderID).ID <= 0
                    || wOrderRouteMap.get(wOrderID).PartList == null || wOrderRouteMap.get(wOrderID).PartList.size() == 0) {
                continue;
            }
            wFPCRoutePartListMap.put(wOrderRouteMap.get(wOrderID).ID, wOrderRouteMap.get(wOrderID).PartList);

            for (FPCRoutePart wFPCRoutePart : wOrderRouteMap.get(wOrderID).PartList) {
                if (wRoutePartMap.containsKey(wFPCRoutePart.ID))
                    continue;
                wRoutePartMap.put(wFPCRoutePart.ID, wFPCRoutePart);
            }
        }


        List<APSTaskPart> wStartTaskList = iPlantSchedulePartTools.getInstance().GetStartedTaskList(wLoginUser, wOrderList, wAPSShiftPeriod,
                wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap);


        Map<Integer, Double> wWMSStockMap = new HashMap<>();
        //检查物料库存  未配料但已计划的物料
        iPlantSchedulePartTools.getInstance().CheckPartMaterials(wLoginUser, wOrderList, wStartTaskList, wMaterilIDList, wOrderRouteMap, wWMSStockMap, wCheckMaterial, wAPSMessage);
        if (wOrderList.size() == 0) {
            return wResult;
        }

        //获取实体不可用记录
        List<FMCUnavailable> wUnavailableList = FMCUnavailableDAO.getInstance().GetUnavailableList(wLoginUser, wStartDate, wEndDate, wErrorCode);

        Map<Integer, List<FMCUnavailable>> wUnavailableMap = iPlantScheduleUtils.getInstance().GetEntityUnavailableList(wUnavailableList, wFPCRouteType);

        //获取实体剩余产能
        Map<Integer, APSMarginEntityCapacity> wMarginEntityCapacityMap = iPlantSchedulePartTools.getInstance().GetMarginCapacityList(wLoginUser,
                wFPCRouteType, wStartTaskList, wEntityIDList, wFPCProductMap, wUnavailableMap, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wAPSMessage);


        wResult = iPlantScheduleUtils.getInstance().APS_AutoTaskPart(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap, wMarginEntityCapacityMap,
                wAllowTechDelayHour, wAPSMessage, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1));

        if (wAPSShiftPeriod == APSShiftPeriod.Week)
            this.APS_AutoTaskPartPoint(wLoginUser, wFPCRouteType, wResult, wRoutePartMap, wAPSShiftPeriod, wStartDate, wEndDate, wAllowTechDelayHour, wAPSMessage);

        return wResult;
    }


    public List<APSMessage> APS_CheckPartMessage(BMSEmployee wLoginUser, List<APSTaskPart> wAPSTaskPartList,
                                                 FPCRouteType wFPCRouteType, List<OMSOrder> wOrderList, APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate, Calendar wEndDate) {
        List<APSMessage> wAPSMessages = new ArrayList<>();

        if (wOrderList == null || wOrderList.size() == 0)
            return wAPSMessages;

        wOrderList.removeIf(p -> p == null || p.ID <= 0);

        if (wOrderList.size() == 0) {
            wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", "未输入相关订单，无法检查！"));
            return wAPSMessages;
        }
        //region  检查修改后与订单列表差异 是否多了某些订单。或少了某些订单的计划；
        List<String> wOrderNoList = wAPSTaskPartList.stream().map(p -> p.OrderNo).distinct().collect(Collectors.toList());
        List<OMSOrder> wOrderList2 = new ArrayList<>();
        List<String> wOrderNoList2 = new ArrayList<>();
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wOrderNoList.contains(wOMSOrder.Code)) {
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", StringUtils.Format("订单:{0}无计划，无需检查！", wOMSOrder.Code)));
                continue;
            }
            wOrderNoList2.add(wOMSOrder.Code);
            wOrderList2.add(wOMSOrder);
        }
        wOrderList = wOrderList2;

        for (String wOrderNo : wOrderNoList) {
            if (wOrderNoList2.contains(wOrderNo)) {
                continue;
            }
            wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", StringUtils.Format("订单:{0}未输入！", wOrderNo)));
        }
        //endregion


        OutResult<Integer> wErrorCode = new OutResult<>(0);


        Map<Integer, FPCRoute> wOrderRouteMap = new HashMap<>();
        Map<Integer, FPCProduct> wFPCProductMap = new HashMap<>();
        List<Integer> wEntityIDList = new ArrayList<>();

        List<Integer> wMaterilIDList = new ArrayList<>();
        //region 检查工艺完整性
        iPlantScheduleUtils.getInstance().CheckOrderRoute(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRouteType, wEntityIDList, wMaterilIDList, wOrderRouteMap, wFPCProductMap, wAPSMessages);
        if (wOrderList.size() == 0) {
            //订单工艺不齐，无法检查后续数据
            return wAPSMessages;
        }
        //endregion

        wEntityIDList = wEntityIDList.stream().distinct().collect(Collectors.toList());
        wEntityIDList.removeIf(p -> p <= 0);

        wMaterilIDList = wMaterilIDList.stream().distinct().collect(Collectors.toList());
        wMaterilIDList.removeIf(p -> p <= 0);


        //region 删除不在订单中的计划
        List<String> wOrderNoList3 = wOrderList.stream().map(p -> p.Code).distinct().collect(Collectors.toList());
        wAPSTaskPartList.removeIf(p -> !wOrderNoList3.contains(p));
        //endregion

        //region  生成剩余加工能力
        Map<Integer, List<List<FPCRoutePart>>> wMarginRoutePartListMap = new HashMap<Integer, List<List<FPCRoutePart>>>();
        Map<Integer, List<FPCRoutePart>> wFPCRoutePartListMap = new HashMap<>();
        Map<Integer, List<APSTaskPart>> wAPSTaskPartDoneListMap = new HashMap<>();


        //生成工艺Map
        for (Integer wOrderID : wOrderRouteMap.keySet()) {
            wFPCRoutePartListMap.put(wOrderRouteMap.get(wOrderID).ID, wOrderRouteMap.get(wOrderID).PartList);
        }

        //获取已排程计划（包含本订单已排程不变计划）
        List<APSTaskPart> wStartTaskList = iPlantSchedulePartTools.getInstance().GetStartedTaskList(wLoginUser, wOrderList, wAPSShiftPeriod,
                wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap);


        Map<Integer, Double> wWMSStockMap = new HashMap<>();
        //检查物料库存  未配料但已计划的物料
        iPlantSchedulePartTools.getInstance().CheckPlanPartMaterials(wLoginUser, wStartTaskList, wAPSTaskPartList, wMaterilIDList, wOrderRouteMap, wWMSStockMap, wAPSMessages);


        //获取实体不可用记录
        List<FMCUnavailable> wUnavailableList = FMCUnavailableDAO.getInstance().GetUnavailableList(wLoginUser, wStartDate, wEndDate, wErrorCode);

        Map<Integer, List<FMCUnavailable>> wUnavailableMap = iPlantScheduleUtils.getInstance().GetEntityUnavailableList(wUnavailableList, wFPCRouteType);

        //获取实体剩余产能
        Map<Integer, APSMarginEntityCapacity> wMarginEntityCapacityMap = iPlantSchedulePartTools.getInstance().GetMarginCapacityList(wLoginUser,
                wFPCRouteType, wStartTaskList, wEntityIDList, wFPCProductMap, wUnavailableMap, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wAPSMessages);

        //endregion

        //region 如果是周计划需要检查与月计划的差别显示延期或提前消息；
        String wKeyTemp = "";
        if (wAPSShiftPeriod == APSShiftPeriod.Week) {
            //查询月计划列表
            List<APSTaskPart> wOrderPartMonthList = APSTaskPartDAO.getInstance().GetAPSTaskPartList(wLoginUser,
                    APSShiftPeriod.Month, wOrderList.stream().map(p -> p.ID).distinct().collect(Collectors.toList()), wErrorCode);

            Map<String, APSTaskPart> wOrderPartMonthMap = wOrderPartMonthList.stream().collect(Collectors.toMap(p -> p.OrderID + "_" + p.RoutePartID, p -> p, (o1, o2) -> o1));

            for (APSTaskPart wAPSTaskPart : wAPSTaskPartList) {
                wKeyTemp = wAPSTaskPart.OrderID + "_" + wAPSTaskPart.RoutePartID;
                if (!wOrderPartMonthMap.containsKey(wKeyTemp) || wOrderPartMonthMap.get(wKeyTemp) == null || wOrderPartMonthMap.get(wKeyTemp).ID <= 0) {

                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPart.OrderID, wAPSTaskPart.LineID,
                            wAPSTaskPart.ProductCode, StringUtils.Format("订单：{0} 工艺：{1} 周排程前未排程对应月计划！",
                            wAPSTaskPart.OrderNo, wAPSTaskPart.RoutePartName)));
                    continue;
                }
                if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartStartTime, wAPSTaskPart.PartStartTime) > 0) {
                    //开始时间提前
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPart.OrderID, wAPSTaskPart.LineID,
                            wAPSTaskPart.ProductCode, StringUtils.Format("订单：{0} 工艺：{1} 周排程对应月计划开始时间提前！",
                            wAPSTaskPart.OrderNo, wAPSTaskPart.RoutePartName)));
                } else if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartStartTime, wAPSTaskPart.PartStartTime) < 0) {
                    //开始时间延后
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPart.OrderID, wAPSTaskPart.LineID,
                            wAPSTaskPart.ProductCode, StringUtils.Format("订单：{0} 工艺：{1} 周排程对应月计划开始时间延后！",
                            wAPSTaskPart.OrderNo, wAPSTaskPart.RoutePartName)));
                }
                if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartEndTime, wAPSTaskPart.PartEndTime) > 0) {
                    //结束时间提前
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPart.OrderID, wAPSTaskPart.LineID,
                            wAPSTaskPart.ProductCode, StringUtils.Format("订单：{0} 工艺：{1} 周排程对应月计划结束时间提前！",
                            wAPSTaskPart.OrderNo, wAPSTaskPart.RoutePartName)));

                } else if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartEndTime, wAPSTaskPart.PartEndTime) < 0) {
                    //结束时间延后
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPart.OrderID, wAPSTaskPart.LineID,
                            wAPSTaskPart.ProductCode, StringUtils.Format("订单：{0} 工艺：{1} 周排程对应月计划结束时间延后！",
                            wAPSTaskPart.OrderNo, wAPSTaskPart.RoutePartName)));
                }
            }

        }
        //endregion

        //region 整体计划与订单预计时间的差别，显示延期或提前的消息；

        Map<Integer, List<APSTaskPart>> wAPSTaskPartListMap = wAPSTaskPartList.stream().collect(Collectors.groupingBy(p -> p.OrderID));

        Calendar wPartStartTime;
        Calendar wPartEndTime;
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wAPSTaskPartListMap.containsKey(wOMSOrder.ID)
                    || wAPSTaskPartListMap.get(wOMSOrder.ID) == null
                    || wAPSTaskPartListMap.get(wOMSOrder.ID).size() == 0)
                continue;
            wPartStartTime = wAPSTaskPartListMap.get(wOMSOrder.ID).stream().min(Comparator.comparing(o -> o.PartStartTime)).get().PartStartTime;
            wPartEndTime = wAPSTaskPartListMap.get(wOMSOrder.ID).stream().max(Comparator.comparing(o -> o.PartEndTime)).get().PartEndTime;

            if (CalendarTool.CompareDate(wOMSOrder.PlanReceiveDate, wPartStartTime) > 0) {
                //开始时间提前
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划开始时间提前！",
                        wOMSOrder.Code)));
            } else if (CalendarTool.CompareDate(wOMSOrder.PlanReceiveDate, wPartStartTime) < 0) {
                //开始时间延后
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划开始时间延后！",
                        wOMSOrder.Code)));
            }
            if (CalendarTool.CompareDate(wOMSOrder.PlanFinishDate, wPartEndTime) > 0) {
                //结束时间提前
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划结束时间提前！",
                        wOMSOrder.Code)));

            } else if (CalendarTool.CompareDate(wOMSOrder.PlanFinishDate, wPartEndTime) < 0) {
                //结束时间延后
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划结束时间延后！",
                        wOMSOrder.Code)));
            }
        }
        //endregion

        //region 循环待排计划 消减剩余产能  在计划时间内未能消减完成提示哪天产能超负荷，或者是休息日 ；
        APSMarginCapacityAndResult wAPSMarginCapacityAndResult = new APSMarginCapacityAndResult(wMarginEntityCapacityMap, true);

        float wMarginF = 0.0f;
        Map<Integer, Map<String, List<Integer>>> wEntitysFullMap = new HashMap<>();
        for (APSTaskPart wAPSTaskPart : wAPSTaskPartList) {
            if (wAPSTaskPart == null || wAPSTaskPart.PlanDayNum == null || wAPSTaskPart.PlanDayNum.size() == 0)
                continue;
            if (!wEntitysFullMap.containsKey(wAPSTaskPart.ProductID))
                wEntitysFullMap.put(wAPSTaskPart.ProductID, new HashMap<>());
            for (String wDateString : wAPSTaskPart.PlanDayNum.keySet()) {
                wMarginF = wAPSTaskPart.PlanDayNum.get(wDateString);
                wMarginF -= wAPSMarginCapacityAndResult.UseCapacity(StringUtils.parseListArgs(wAPSTaskPart.getPartEntityID()),
                        wAPSTaskPart.ProductID,wAPSTaskPart.RoutePartID, StringUtils.parseCalendar(wDateString, "yyyy-MM-dd"), wMarginF);

                if (wMarginF <= 0)
                    continue;
                if (!wEntitysFullMap.get(wAPSTaskPart.ProductID).containsKey(wDateString))
                    wEntitysFullMap.get(wAPSTaskPart.ProductID).put(wDateString, new ArrayList<>());

                if (!wEntitysFullMap.get(wAPSTaskPart.ProductID).get(wDateString).contains(wAPSTaskPart.getPartEntityID())) {

                    wEntitysFullMap.get(wAPSTaskPart.ProductID).get(wDateString).add(wAPSTaskPart.getPartEntityID());
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Capacity.getValue(), 0, 0,
                            wAPSTaskPart.ProductCode, StringUtils.Format("{0}：{1}在{2}生产{3}产品，产能超负荷，可调整产能或班次日历！",
                            wFPCRouteType.getLabel(), wAPSTaskPart.getPartEntityName(), wDateString, wAPSTaskPart.ProductCode)));
                    //增加提示
                }
            }
        }
        //endregion

        return wAPSMessages;
    }


    /**
     * 工序
     *
     * @param wLoginUser
     * @param wOrderList
     * @param wAPSShiftPeriod
     * @param wStartDate
     * @param wEndDate
     * @param wCheckMaterial
     * @param wAllowTechDelayHour
     * @param wAPSMessage
     * @return
     */
    public List<APSTaskPart> APS_AutoTaskPartPoint(BMSEmployee wLoginUser, List<OMSOrder> wOrderList,
                                                        APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate, Calendar wEndDate,
                                                        boolean wCheckMaterial, int wAllowTechDelayHour, List<APSMessage> wAPSMessage) {
        List<APSTaskPart> wResult = new ArrayList<>();
        if (wOrderList == null || wOrderList.size() == 0)
            return wResult;

        wOrderList.removeIf(p -> p == null || p.ID <= 0);

        if (wOrderList.size() == 0)
            return wResult;
        OutResult<Integer> wErrorCode = new OutResult<>(0);
        FPCRouteType wFPCRouteType = FPCRouteType.RoutePartPoint;

        Map<Integer, FPCRoute> wOrderRouteMap = new HashMap<>();
        Map<Integer, FPCProduct> wFPCProductMap = new HashMap<>();
        List<Integer> wEntityIDList = new ArrayList<>();
        List<Integer> wMaterilIDList = new ArrayList<>();
        //step1:检查订单工艺
        iPlantScheduleUtils.getInstance().CheckOrderRoute(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRouteType, wEntityIDList, wMaterilIDList, wOrderRouteMap, wFPCProductMap, wAPSMessage);
        if (wOrderList.size() == 0) {
            return wResult;
        }

        Map<Integer, List<List<FPCRoutePartPoint>>> wMarginRoutePartListMap = new HashMap<Integer, List<List<FPCRoutePartPoint>>>();
        Map<Integer, List<FPCRoutePartPoint>> wFPCRoutePartPointListMap = new HashMap<>();
        Map<Integer, List<FPCRoutePart>> wFPCRoutePartListMap= new HashMap<>();
        Map<Integer, List<APSTaskPartPoint>> wAPSTaskPartDoneListMap = new HashMap<>();

        for (Integer wRouteID : wOrderRouteMap.keySet()) {

            wFPCRoutePartPointListMap.put(wRouteID, new ArrayList<FPCRoutePartPoint>());
            if (wOrderRouteMap.get(wRouteID) == null || wOrderRouteMap.get(wRouteID).PartList == null)
                continue;
            wFPCRoutePartListMap.put(wRouteID,wOrderRouteMap.get(wRouteID).PartList);
            for (FPCRoutePart wFPCRoutePart : wOrderRouteMap.get(wRouteID).PartList) {
                wFPCRoutePartPointListMap.get(wRouteID).addAll(wFPCRoutePart.PartPointList);
            }
        }


        //step2: 检查已排产信息 生成剩余工艺 返回订单已执行计划wAPSTaskPartDoneListMap  时间段内已排计划wStartTaskList包含已执行
        //注意： 周计划已执行计划的部分不能再排，  月计划已执行的整个订单不允许再排
        List<APSTaskPartPoint> wStartTaskList = iPlantSchedulePartPointTools.getInstance().GetStartedTaskPointList(wLoginUser, wOrderList, wAPSShiftPeriod,
                wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wFPCRoutePartPointListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap);


        Map<Integer, Double> wWMSStockMap = new HashMap<>();
        //step3: 检查物料库存  未配料但已计划的物料
        iPlantSchedulePartPointTools.getInstance().CheckPartPointMaterials(wLoginUser, wOrderList, wStartTaskList, wMaterilIDList, wOrderRouteMap, wWMSStockMap, wCheckMaterial, wAPSMessage);
        if (wOrderList.size() == 0) {
            return wResult;
        }


        //获取实体不可用记录
        List<FMCUnavailable> wUnavailableList = FMCUnavailableDAO.getInstance().GetUnavailableList(wLoginUser, wStartDate, wEndDate, wErrorCode);

        Map<Integer, List<FMCUnavailable>> wUnavailableMap = iPlantScheduleUtils.getInstance().GetEntityUnavailableList(wUnavailableList, wFPCRouteType);

        //step4:获取实体剩余产能
        Map<Integer, APSMarginEntityCapacity> wMarginEntityCapacityMap = iPlantSchedulePartPointTools.getInstance().GetPointMarginCapacityList(wLoginUser,
                wFPCRouteType, wStartTaskList, wEntityIDList, wFPCProductMap, wUnavailableMap, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wAPSMessage);


        return iPlantScheduleUtils.getInstance().APS_AutoTaskPartPoint(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRoutePartPointListMap,wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap, wMarginEntityCapacityMap,
                wAllowTechDelayHour, wAPSMessage, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays));
    }


    /**
     * 工序
     *
     * @param wLoginUser
     * @param wAPSShiftPeriod
     * @param wStartDate
     * @param wEndDate
     * @param wAllowTechDelayHour
     * @param wAPSMessage
     * @return
     */
    public List<APSTaskPartPoint> APS_AutoTaskPartPoint(BMSEmployee wLoginUser, FPCRouteType wFPCRouteType, List<APSTaskPart> wAPSTaskPartList, Map<Integer, FPCRoutePart> wRoutePartMap,
                                                        APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate, Calendar wEndDate,
                                                        int wAllowTechDelayHour, List<APSMessage> wAPSMessage) {
        List<APSTaskPartPoint> wResult = new ArrayList<>();
        if (wAPSTaskPartList == null || wAPSTaskPartList.size() == 0)
            return wResult;

        wAPSTaskPartList.removeIf(p -> p == null);

        if (wAPSTaskPartList.size() == 0)
            return wResult;
        OutResult<Integer> wErrorCode = new OutResult<>(0);

        Map<Integer, FPCProduct> wFPCProductMap = new HashMap<>();

        List<Integer> wEntityIDList = new ArrayList<>();
        List<Integer> wMaterilIDList = new ArrayList<>();
        //step1:检查订单工艺
        iPlantScheduleUtils.getInstance().CheckRoutePart(wLoginUser, wAPSTaskPartList, wAPSShiftPeriod, wEntityIDList, wMaterilIDList, wRoutePartMap, wFPCProductMap, wAPSMessage);


        Map<String, List<List<FPCRoutePartPoint>>> wMarginRoutePartListMap = new HashMap<String, List<List<FPCRoutePartPoint>>>();
        Map<Integer, List<FPCRoutePartPoint>> wFPCRoutePartListMap = new HashMap<>();
        Map<String, List<APSTaskPartPoint>> wAPSTaskPartDoneListMap = new HashMap<>();

        for (Integer wRoutePartID : wRoutePartMap.keySet()) {

            wFPCRoutePartListMap.put(wRoutePartID, new ArrayList<FPCRoutePartPoint>());
            if (wRoutePartMap.get(wRoutePartID) == null || wRoutePartMap.get(wRoutePartID).PartPointList == null)
                continue;
            wFPCRoutePartListMap.get(wRoutePartID).addAll(wRoutePartMap.get(wRoutePartID).PartPointList);
        }


        //step2: 检查已排产信息 生成剩余工艺 返回订单已执行计划wAPSTaskPartDoneListMap  时间段内已排计划wStartTaskList包含已执行
        //注意：
        List<APSTaskPartPoint> wStartTaskList = iPlantSchedulePartPointTools.getInstance().GetStartedTaskPointListByTaskPart(wLoginUser, wAPSTaskPartList, wAPSShiftPeriod,
                wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap);


        if (wFPCRouteType == FPCRouteType.RoutePartStation) {
            return iPlantSchedulePartPointTools.getInstance().CreateAPSTaskPartPointList(wLoginUser, wAPSShiftPeriod, wMarginRoutePartListMap, wAPSTaskPartList);
        }

        wFPCRouteType = FPCRouteType.RoutePartPoint;
        //获取实体不可用记录
        List<FMCUnavailable> wUnavailableList = FMCUnavailableDAO.getInstance().GetUnavailableList(wLoginUser, wStartDate, wEndDate, wErrorCode);

        Map<Integer, List<FMCUnavailable>> wUnavailableMap = iPlantScheduleUtils.getInstance().GetEntityUnavailableList(wUnavailableList, wFPCRouteType);

        //step4:获取实体剩余产能
        Map<Integer, APSMarginEntityCapacity> wMarginEntityCapacityMap = iPlantSchedulePartPointTools.getInstance().GetPointMarginCapacityList(wLoginUser,
                FPCRouteType.RoutePartPoint, wStartTaskList, wEntityIDList, wFPCProductMap, wUnavailableMap, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wAPSMessage);


        return iPlantScheduleUtils.getInstance().APS_AutoTaskPartPointByTaskPart(wLoginUser, wAPSTaskPartList, wAPSShiftPeriod, wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap, wMarginEntityCapacityMap,
                wAllowTechDelayHour, wAPSMessage, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays));
    }


    public List<APSMessage> APS_CheckPartPointMessage(BMSEmployee wLoginUser, List<APSTaskPartPoint> wAPSTaskPartPointList,
                                                      FPCRouteType wFPCRouteType, List<OMSOrder> wOrderList, APSShiftPeriod wAPSShiftPeriod, Calendar wStartDate, Calendar wEndDate) {
        List<APSMessage> wAPSMessages = new ArrayList<>();

        if (wOrderList == null || wOrderList.size() == 0)
            return wAPSMessages;

        wOrderList.removeIf(p -> p == null || p.ID <= 0);

        if (wOrderList.size() == 0) {
            wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", "未输入相关订单，无法检查！"));
            return wAPSMessages;
        }
        //region  检查修改后与订单列表差异 是否多了某些订单。或少了某些订单的计划；
        List<String> wOrderNoList = wAPSTaskPartPointList.stream().map(p -> p.OrderNo).distinct().collect(Collectors.toList());
        List<OMSOrder> wOrderList2 = new ArrayList<>();
        List<String> wOrderNoList2 = new ArrayList<>();
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wOrderNoList.contains(wOMSOrder.Code)) {
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", StringUtils.Format("订单:{0}无计划，无需检查！", wOMSOrder.Code)));
                continue;
            }
            wOrderNoList2.add(wOMSOrder.Code);
            wOrderList2.add(wOMSOrder);
        }
        wOrderList = wOrderList2;

        for (String wOrderNo : wOrderNoList) {
            if (wOrderNoList2.contains(wOrderNo)) {
                continue;
            }
            wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), 0, 0, "", StringUtils.Format("订单:{0}未输入！", wOrderNo)));
        }
        //endregion


        OutResult<Integer> wErrorCode = new OutResult<>(0);


        Map<Integer, FPCRoute> wOrderRouteMap = new HashMap<>();
        Map<Integer, FPCProduct> wFPCProductMap = new HashMap<>();
        List<Integer> wEntityIDList = new ArrayList<>();

        List<Integer> wMaterilIDList = new ArrayList<>();
        //region 检查工艺完整性
        iPlantScheduleUtils.getInstance().CheckOrderRoute(wLoginUser, wOrderList, wAPSShiftPeriod, wFPCRouteType, wEntityIDList, wMaterilIDList, wOrderRouteMap, wFPCProductMap, wAPSMessages);
        if (wOrderList.size() == 0) {
            //订单工艺不齐，无法检查后续数据
            return wAPSMessages;
        }
        //endregion

        wEntityIDList = wEntityIDList.stream().distinct().collect(Collectors.toList());
        wEntityIDList.removeIf(p -> p <= 0);

        wMaterilIDList = wMaterilIDList.stream().distinct().collect(Collectors.toList());
        wMaterilIDList.removeIf(p -> p <= 0);


        //region 删除不在订单中的计划
        List<String> wOrderNoList3 = wOrderList.stream().map(p -> p.Code).distinct().collect(Collectors.toList());
        wAPSTaskPartPointList.removeIf(p -> !wOrderNoList3.contains(p));
        //endregion

        //region  生成剩余加工能力
        Map<Integer, List<List<FPCRoutePartPoint>>> wMarginRoutePartListMap = new HashMap<Integer, List<List<FPCRoutePartPoint>>>();
        Map<Integer, List<FPCRoutePartPoint>> wFPCRoutePartListMap = new HashMap<>();
        Map<Integer, List<APSTaskPartPoint>> wAPSTaskPartDoneListMap = new HashMap<>();

        for (Integer wRouteID : wOrderRouteMap.keySet()) {

            wFPCRoutePartListMap.put(wRouteID, new ArrayList<FPCRoutePartPoint>());
            if (wOrderRouteMap.get(wRouteID) == null || wOrderRouteMap.get(wRouteID).PartList == null)
                continue;
            for (FPCRoutePart wFPCRoutePart : wOrderRouteMap.get(wRouteID).PartList) {
                wFPCRoutePartListMap.get(wRouteID).addAll(wFPCRoutePart.PartPointList);
            }
        }

        //获取已排程计划（包含本订单已排程不变计划）
        List<APSTaskPartPoint> wStartTaskList = iPlantSchedulePartPointTools.getInstance().GetStartedTaskPointList(wLoginUser, wOrderList, wAPSShiftPeriod,
                wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wFPCRoutePartListMap, wMarginRoutePartListMap, wAPSTaskPartDoneListMap);


        Map<Integer, Double> wWMSStockMap = new HashMap<>();
        //检查物料库存  未配料但已计划的物料
        iPlantSchedulePartPointTools.getInstance().CheckPlanPartPointMaterials(wLoginUser, wStartTaskList, wAPSTaskPartPointList, wMaterilIDList, wOrderRouteMap, wWMSStockMap, wAPSMessages);


        //获取实体不可用记录
        List<FMCUnavailable> wUnavailableList = FMCUnavailableDAO.getInstance().GetUnavailableList(wLoginUser, wStartDate, wEndDate, wErrorCode);

        Map<Integer, List<FMCUnavailable>> wUnavailableMap = iPlantScheduleUtils.getInstance().GetEntityUnavailableList(wUnavailableList, wFPCRouteType);

        //获取实体剩余产能
        Map<Integer, APSMarginEntityCapacity> wMarginEntityCapacityMap = iPlantSchedulePartPointTools.getInstance().GetPointMarginCapacityList(wLoginUser,
                wFPCRouteType, wStartTaskList, wEntityIDList, wFPCProductMap, wUnavailableMap, wStartDate, CalendarTool.AddCalendar(wEndDate, Calendar.DAY_OF_MONTH, DefaultDelayDays + 1), wAPSMessages);

        //endregion

        //region 如果是周计划需要检查与月计划的差别显示延期或提前消息；
        String wKeyTemp = "";
        if (wAPSShiftPeriod == APSShiftPeriod.Week) {
            //查询月计划列表
            List<APSTaskPartPoint> wOrderPartMonthList = APSTaskPartPointDAO.getInstance().GetAPSTaskPartPointList(wLoginUser,
                    APSShiftPeriod.Month, wOrderList.stream().map(p -> p.ID).distinct().collect(Collectors.toList()), wErrorCode);

            Map<String, APSTaskPartPoint> wOrderPartMonthMap = wOrderPartMonthList.stream().collect(Collectors.toMap(p -> p.OrderID + "_" + p.RoutePartPointID, p -> p, (o1, o2) -> o1));

            for (APSTaskPartPoint wAPSTaskPartPoint : wAPSTaskPartPointList) {
                wKeyTemp = wAPSTaskPartPoint.OrderID + "_" + wAPSTaskPartPoint.RoutePartPointID;
                if (!wOrderPartMonthMap.containsKey(wKeyTemp) || wOrderPartMonthMap.get(wKeyTemp) == null || wOrderPartMonthMap.get(wKeyTemp).ID <= 0) {

                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPartPoint.OrderID, wAPSTaskPartPoint.LineID,
                            wAPSTaskPartPoint.ProductCode, StringUtils.Format("订单：{0} 工序：{1} 周排程前未排程对应月计划！",
                            wAPSTaskPartPoint.OrderNo, wAPSTaskPartPoint.RoutePartPointName)));
                    continue;
                }
                if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartPointStartTime, wAPSTaskPartPoint.PartPointStartTime) > 0) {
                    //开始时间提前
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPartPoint.OrderID, wAPSTaskPartPoint.LineID,
                            wAPSTaskPartPoint.ProductCode, StringUtils.Format("订单：{0} 工序：{1} 周排程对应月计划开始时间提前！",
                            wAPSTaskPartPoint.OrderNo, wAPSTaskPartPoint.RoutePartPointName)));
                } else if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartPointStartTime, wAPSTaskPartPoint.PartPointStartTime) < 0) {
                    //开始时间延后
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPartPoint.OrderID, wAPSTaskPartPoint.LineID,
                            wAPSTaskPartPoint.ProductCode, StringUtils.Format("订单：{0} 工序：{1} 周排程对应月计划开始时间延后！",
                            wAPSTaskPartPoint.OrderNo, wAPSTaskPartPoint.RoutePartPointName)));
                }
                if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartPointEndTime, wAPSTaskPartPoint.PartPointEndTime) > 0) {
                    //结束时间提前
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPartPoint.OrderID, wAPSTaskPartPoint.LineID,
                            wAPSTaskPartPoint.ProductCode, StringUtils.Format("订单：{0} 工序：{1} 周排程对应月计划结束时间提前！",
                            wAPSTaskPartPoint.OrderNo, wAPSTaskPartPoint.RoutePartPointName)));

                } else if (CalendarTool.CompareDate(wOrderPartMonthMap.get(wKeyTemp).PartPointEndTime, wAPSTaskPartPoint.PartPointEndTime) < 0) {
                    //结束时间延后
                    wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wAPSTaskPartPoint.OrderID, wAPSTaskPartPoint.LineID,
                            wAPSTaskPartPoint.ProductCode, StringUtils.Format("订单：{0} 工序：{1} 周排程对应月计划结束时间延后！",
                            wAPSTaskPartPoint.OrderNo, wAPSTaskPartPoint.RoutePartPointName)));
                }
            }

        }
        //endregion

        //region 整体计划与订单预计时间的差别，显示延期或提前的消息；

        Map<Integer, List<APSTaskPartPoint>> wAPSTaskPartPointListMap = wAPSTaskPartPointList.stream().collect(Collectors.groupingBy(p -> p.OrderID));

        Calendar wPartStartTime;
        Calendar wPartEndTime;
        for (OMSOrder wOMSOrder : wOrderList) {
            if (!wAPSTaskPartPointListMap.containsKey(wOMSOrder.ID)
                    || wAPSTaskPartPointListMap.get(wOMSOrder.ID) == null
                    || wAPSTaskPartPointListMap.get(wOMSOrder.ID).size() == 0)
                continue;
            wPartStartTime = wAPSTaskPartPointListMap.get(wOMSOrder.ID).stream().min(Comparator.comparing(o -> o.PartPointStartTime)).get().PartPointStartTime;
            wPartEndTime = wAPSTaskPartPointListMap.get(wOMSOrder.ID).stream().max(Comparator.comparing(o -> o.PartPointEndTime)).get().PartPointEndTime;

            if (CalendarTool.CompareDate(wOMSOrder.PlanReceiveDate, wPartStartTime) > 0) {
                //开始时间提前
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划开始时间提前！",
                        wOMSOrder.Code)));
            } else if (CalendarTool.CompareDate(wOMSOrder.PlanReceiveDate, wPartStartTime) < 0) {
                //开始时间延后
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划开始时间延后！",
                        wOMSOrder.Code)));
            }
            if (CalendarTool.CompareDate(wOMSOrder.PlanFinishDate, wPartEndTime) > 0) {
                //结束时间提前
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划结束时间提前！",
                        wOMSOrder.Code)));

            } else if (CalendarTool.CompareDate(wOMSOrder.PlanFinishDate, wPartEndTime) < 0) {
                //结束时间延后
                wAPSMessages.add(new APSMessage(APSMsgTypes.Order.getValue(), wOMSOrder.ID, wOMSOrder.LineID,
                        wOMSOrder.ProductCode, StringUtils.Format("订单：{0} 整体计划结束时间延后！",
                        wOMSOrder.Code)));
            }
        }
        //endregion

        //region 循环待排计划 消减剩余产能  在计划时间内未能消减完成提示哪天产能超负荷，或者是休息日 ；
        APSMarginCapacityAndResult wAPSMarginCapacityAndResult = new APSMarginCapacityAndResult(wMarginEntityCapacityMap, true);

        float wMarginF = 0.0f;
        FMCStation wFMCStation;
        Map<Integer, Map<String, List<Integer>>> wEntitysFullMap = new HashMap<>();
        for (APSTaskPartPoint wAPSTaskPart : wAPSTaskPartPointList) {
            if (wAPSTaskPart == null || wAPSTaskPart.PlanDayNum == null || wAPSTaskPart.PlanDayNum.size() == 0)
                continue;
            if (!wEntitysFullMap.containsKey(wAPSTaskPart.ProductID))
                wEntitysFullMap.put(wAPSTaskPart.ProductID, new HashMap<>());

            for (String wDateString : wAPSTaskPart.PlanDayNum.keySet()) {
                wMarginF = wAPSTaskPart.PlanDayNum.get(wDateString);
                wMarginF -= wAPSMarginCapacityAndResult.UseCapacity(wAPSTaskPart.StationID,
                        wAPSTaskPart.ProductID,wAPSTaskPart.RoutePartPointID, StringUtils.parseCalendar(wDateString, "yyyy-MM-dd"), wMarginF);

                if (wMarginF <= 0)
                    continue;
                if (!wEntitysFullMap.get(wAPSTaskPart.ProductID).containsKey(wDateString))
                    wEntitysFullMap.get(wAPSTaskPart.ProductID).put(wDateString, new ArrayList<>());


                for (Integer wStationID : wAPSTaskPart.StationID) {
                    if (wEntitysFullMap.get(wAPSTaskPart.ProductID).get(wDateString).contains(wStationID))
                        continue;
                    wFMCStation = CacheUtils.GetCacheInfo(FMCStation.class, wStationID, "");

                    wAPSMessages.add(new APSMessage(APSMsgTypes.Capacity.getValue(), 0, 0,
                            wAPSTaskPart.ProductCode, StringUtils.Format("工位({0})在{1}生产编号为{2}产品 产能超负荷，可调整产能或班次日历！", wFMCStation.Name, wDateString, wAPSTaskPart.ProductCode)));
                    //增加提示

                    wEntitysFullMap.get(wAPSTaskPart.ProductID).get(wDateString).add(wStationID);
                }

            }
        }
        //endregion

        return wAPSMessages;
    }


    /**
     * ==自动排程  广机旧版
     *
     * @param wOrderPartIssuedList ==订单列表中的订单 已经 下达/开工/暂停 的工位计划 周 月 排月计划时 包含月计划
     *                             排周计划/滚动计划时只包含周计划
     * @param wOutsourceOrderList  ==委外订单列表
     * @param wEndTime             ==排程结束时间
     * @param wRoutePartList       ==工位工艺路径 key:OrderID Value:
     * @param wManuCapacityList    ==工位加工能力明细
     * @param wErrorCode           ==返回错误码
     * @param wMessageList         ==返回冲突消息
     * @param wWorkDay             ==作息
     * @param wAllZoneList         ==作息列表
     * @param wCalendarMap         ==订单休息日
     * @param wMaualTaskList       ==固定工位计划（工位应该连续 ）
     * @param wErrorCode           ==返回错误码
     * @return
     */
    public List<APSTaskPart> APS_AutoTaskPartOld(BMSEmployee wLoginUser, List<OMSOrder> wOrderList,
                                                 APSShiftPeriod wShiftPeriod, List<APSTaskPart> wOrderPartIssuedList,
                                                 List<OMSOutsourceOrder> wOutsourceOrderList, Calendar wStartTime, Calendar wEndTime,
                                                 Map<Integer, List<FPCRoutePart>> wRoutePartList, List<APSManuCapacity> wManuCapacityList,

                                                 List<APSMessage> wMessageList, int wWorkDay, List<FMCTimeZone> wAllZoneList,
                                                 Map<Integer, List<CFGCalendar>> wCalendarMap, int wLimitMinutes, List<APSTaskPart> wMaualTaskList,
                                                 OutResult<Integer> wErrorCode) {

        List<APSTaskPart> wResult = new ArrayList<APSTaskPart>();
        try {
            // 安装拆解只为算出安装工位是否能正常排程
            // 考虑当前订单上次排程情况 并需要多次时间转序
            // 考虑所排工位剩余容量
            // 一单单排
            wStartTime = CalendarTool.CloneDate(wStartTime);
            wEndTime = CalendarTool.CloneDate(wEndTime);

            Calendar wTempStartTime = (Calendar) wStartTime.clone();
            // 生成开始时间车辆预计占用工位列表


            if (wMaualTaskList == null)
                wMaualTaskList = new ArrayList<>();

            Map<Integer, List<APSTaskPart>> wMaualTaskListMap = wMaualTaskList.stream()
                    .collect(Collectors.groupingBy(p -> p.OrderID));

            // 获取任务结束时间不大于开始时间的所有任务 也就是他们是预计已完成的工位任务


            Map<Integer, List<APSTaskPart>> wAPSTaskPartDoneListMap = wOrderPartIssuedList.stream()
                    .filter(p -> p.Active == 1 && (p.Status == APSTaskStatus.Confirm.getValue()
                            || p.Status == APSTaskStatus.Started.getValue()
                            || p.Status == APSTaskStatus.Suspend.getValue() || p.Status == APSTaskStatus.Done.getValue()
                            || p.Status == APSTaskStatus.Aborted.getValue()))
                    .collect(Collectors.groupingBy(p -> p.OrderID));

            // 将工艺路径转换从工艺路径列表
            Map<Integer, List<List<FPCRoutePart>>> wPartRouteListLineMap = new HashMap<Integer, List<List<FPCRoutePart>>>();
            for (Integer wOrderID : wRoutePartList.keySet()) {

                FPCRoutePartUtils wFPCRoutePartUtils = new FPCRoutePartUtils(wRoutePartList.get(wOrderID));
                wPartRouteListLineMap.put(wOrderID, wFPCRoutePartUtils.GetPartOrderRouteList());
            }

            List<APSTaskPart> wOrderTaskPartList = null;
            Map<Integer, APSManuCapacity> wManuCapacityMap = null;
            APSManuCapacity wManuCapacity = null;

            List<FPCRoutePart> wRoutePartOrder = null;
            List<OMSOutsourceOrder> wOutsourceList = null;
            Map<Integer, APSTaskPart> wMaualTaskPartMap = null;
            for (OMSOrder wOrder : wOrderList) {
                long wStartMillis = System.currentTimeMillis();

                // 判断固定工位计划是否存在
                if (wMaualTaskListMap.containsKey(wOrder.ID)) {
                    // 存在将固定计划加入已排计划
                    wOrderTaskPartList = wMaualTaskListMap.get(wOrder.ID);
                } else {
                    wOrderTaskPartList = new ArrayList<APSTaskPart>();
                }
                wMaualTaskPartMap = wOrderTaskPartList.stream()
                        .collect(Collectors.toMap(p -> p.RoutePartID, p -> p, (k1, k2) -> k1));

                List<List<FPCRoutePart>> wRoutePartIntegerList = new ArrayList<List<FPCRoutePart>>();

                if (!wPartRouteListLineMap.containsKey(wOrder.getID()))
                    continue;

                if (!wRoutePartList.containsKey(wOrder.getID()))
                    continue;

                wManuCapacityMap = wManuCapacityList.stream().filter(p -> p.LineID == wOrder.LineID).collect(
                        Collectors.toMap(APSManuCapacity::getPartID, Function.identity(), (key1, key2) -> key2));

                wOutsourceList = wOutsourceOrderList.stream().filter(p -> p.OrderID == wOrder.getID())
                        .collect(Collectors.toList());

                wRoutePartOrder = wRoutePartList.get(wOrder.getID());

                wRoutePartIntegerList = wPartRouteListLineMap.get(wOrder.getID());

                // 获取此订单已做工位列表
                List<APSTaskPart> wDoneStationList = new ArrayList<APSTaskPart>();
                if (wAPSTaskPartDoneListMap.containsKey(wOrder.getID()))
                    wDoneStationList = wAPSTaskPartDoneListMap.get(wOrder.getID());

                // Map<Integer, APSTaskPart> wUndoOrUnStartTaskList = new HashMap<Integer,
                // APSTaskPart>();

                // 根据初始工位列表&工艺路径列表&已做工位列表 获取剩余工艺路径列表
//				List<List<Integer>> wPartRouteList = FPCRoutePartUtils.GetUndoStationRouteList(wLoginUser,
//						wDoneStationList, wManuCapacityMap, wRoutePartIntegerList, wWorkDay, wUndoOrUnStartTaskList,
//						wAllZoneList, wCalendarMap);

                // 根据初始工位列表&工艺路径列表&已排程工位列表 获取剩余未排程工艺路径列表
                List<List<FPCRoutePart>> wPartRouteList = FPCRoutePartUtils.GetUndoStationRouteList(wLoginUser,
                        wDoneStationList, wRoutePartIntegerList);

                Calendar wOrderStartTime = (Calendar) wStartTime.clone(); // 需要考虑当天是否上班 以及考虑班次工时问题 确定开始时间

                Calendar wStartClone = (Calendar) wStartTime.clone();

                // 预计进厂日期
                if ((wOrder.Status == OMSOrderStatus.HasOrder.getValue()
                        || wOrder.Status == OMSOrderStatus.ProductReady.getValue()
                        || wOrder.Status == OMSOrderStatus.PlanReady.getValue())
                        && wOrderStartTime.compareTo(wOrder.PlanReceiveDate) < 0) {

                    wStartClone = (Calendar) wOrder.PlanReceiveDate.clone();
                }

                for (List<FPCRoutePart> wRoutePartOneList : wPartRouteList) {

                    wOrderStartTime = (Calendar) wStartClone.clone();

                    for (FPCRoutePart wRoutePart : wRoutePartOneList) {

//						if (wInteger == 5 && wOrder.ID == 53) {
//							System.out.println();
//						}

                        if (wManuCapacityMap.containsKey(wRoutePart.getPartEntityID())) {
                            wManuCapacity = wManuCapacityMap.get(wRoutePart.getPartEntityID());
                        } else {
                            wManuCapacity = new APSManuCapacity();
                            //  wManuCapacity.setPartID(wInteger);
                        }

                        Optional<APSTaskPart> wOptional = null;
                        APSTaskPart wAPSTaskPart = new APSTaskPart();

//						if (i == 1 && wUndoOrUnStartTaskList.containsKey(wInteger)) {
//
//							wAPSTaskPart = wUndoOrUnStartTaskList.get(wInteger);
//						} else {
                        // wAPSTaskPart = new APSTaskPart();
                        // }

                        // 判断此工位是否是已固定任务
                        if (wMaualTaskPartMap.containsKey(wRoutePart)) {
                            wOrderStartTime = (Calendar) wMaualTaskPartMap.get(wRoutePart).PartEndTime.clone();
                            continue;
                        }

                        if (wAPSTaskPart.RoutePartID <= 0) {

                            // 判断是否有已排任务 有则获取已排任务
                            // 是否为合并路径工位点 若是 则获取已排任务 判断其开始时间大小 并修改此开始时间为 比较大的值 相应修改其后已排任务的开始结束时间
                            wOptional = wOrderTaskPartList.stream().filter(p -> p.RoutePartID == wRoutePart.ID).findFirst();
                            // 若已排过此工位任务就需要判断是否是合并工位 若是需要比对开始时间并修改
                            if (wOptional.isPresent()) {
                                wAPSTaskPart = wOptional.get();
                                // 是否为合并路径工位点
//                                if (!CheckMergeRoutePart(wRoutePartOrder, wInteger)) {
//                                    wOrderStartTime = (Calendar) wAPSTaskPart.PartEndTime.clone();
//                                    // 否 不做任何处理
//                                    continue;
//                                }
                                // 是判断是否与此时间有不同
                                if (wOrderStartTime.compareTo(wAPSTaskPart.PartStartTime) <= 0) {
                                    // 时间比之前小 也不做任何处理
                                    wOrderStartTime = (Calendar) wAPSTaskPart.PartEndTime.clone();
                                    continue;
                                }
                                // 时间比之前大 修改其开始和结束时间 并影响其后所有工位任务
                                wAPSTaskPart.PartStartTime = (Calendar) wOrderStartTime.clone();
                                EditTaskTime(wLoginUser, wAPSTaskPart, wShiftPeriod, wRoutePartOrder, wOutsourceList,
                                        (Calendar) wOrderStartTime.clone(), wOrderTaskPartList, wResult, wManuCapacity,
                                        wOrderPartIssuedList, wManuCapacityMap, wWorkDay, true, wMessageList, wAllZoneList,
                                        wCalendarMap.get(wOrder.LineID), wMaualTaskPartMap, wLimitMinutes);

                                wOrderStartTime = (Calendar) wAPSTaskPart.PartEndTime.clone();
                                continue;

                            } else {
//                                wAPSTaskPart = new APSTaskPart(wLoginUser, wManuCapacity, wOrder,
//                                        wShiftPeriod.getValue());
                                wAPSTaskPart.PartStartTime = (Calendar) wOrderStartTime.clone();
                            }
                        }

                        this.EditTaskTime(wLoginUser, wAPSTaskPart, wShiftPeriod, wRoutePartOrder, wOutsourceList,
                                (Calendar) wOrderStartTime.clone(), wOrderTaskPartList, wResult, wManuCapacity,
                                wOrderPartIssuedList, wManuCapacityMap,
                                wWorkDay, false, wMessageList, wAllZoneList, wCalendarMap.get(wOrder.LineID),
                                wMaualTaskPartMap, wLimitMinutes);

                        if (wAPSTaskPart.ID > 0) {
                            wAPSTaskPart.ID = 0;
                            if (wAPSTaskPart.PartStartTime.compareTo(wStartTime) < 0)
                                wAPSTaskPart.PartStartTime = (Calendar) wStartTime.clone();
//							wAPSTaskPart.ShiftID = MESServer.MES_QueryShiftID(wLoginUser.CompanyID,
//									(Calendar) wStartTime.clone(), wShiftPeriod, FMCShiftLevel.Default, 0);
                        }

                        wOrderTaskPartList.add(wAPSTaskPart);
                        wOrderStartTime = (Calendar) wAPSTaskPart.PartEndTime.clone();

                    }

                }
                long wEndMillis = System.currentTimeMillis();
                int wCallMS = (int) (wEndMillis - wStartMillis);
                logger.info("排程 {0} {1}", "Step__" + wOrder.ID, wCallMS);
                wResult.addAll(wOrderTaskPartList);
            }

            /*
             * 避免时间超过 删除原因：避免工位任务没有出来 Calendar wEndTimeMax = (Calendar) wEndTime.clone();
             * wEndTimeMax.add(Calendar.DAY_OF_MONTH, 1); wResult =
             * wResult.stream().filter(p -> p.StartTime.compareTo(wEndTimeMax) <
             * 0).collect(Collectors.toList());
             *
             * wResult.forEach(p -> { if (p.EndTime.compareTo(wEndTimeMax) > 0) p.EndTime =
             * (Calendar) wEndTimeMax.clone(); });
             */

        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.toString());
        }
        return wResult;
    }


    /**
     * @param wLoginUser
     * @param wTaskPart
     * @param wShiftPeriod
     * @param wRoutePartList
     * @param wOutsourceList
     * @param wStartime
     * @param wBuilderTaskList
     * @param wStationAllTimeList
     * @param wManuCapacity
     * @param wOrderPartIssuedList
     * @param wManuCapacityMap
     * @param wWorkDay
     * @param wIsMergeNext
     * @param wAPSMessageList
     * @param wAllZoneList
     * @param wMaualTaskPartMap    固定任务 防止后续关联修改
     * @param wLimitMinutes
     */
    private void EditTaskTime(BMSEmployee wLoginUser, APSTaskPart wTaskPart, APSShiftPeriod wShiftPeriod,
                              List<FPCRoutePart> wRoutePartList, List<OMSOutsourceOrder> wOutsourceList, Calendar wStartime,
                              List<APSTaskPart> wBuilderTaskList, List<APSTaskPart> wStationAllTimeList, APSManuCapacity wManuCapacity,
                              List<APSTaskPart> wOrderPartIssuedList, Map<Integer, APSManuCapacity> wManuCapacityMap,
                              int wWorkDay,
                              boolean wIsMergeNext, List<APSMessage> wAPSMessageList, List<FMCTimeZone> wAllZoneList,
                              List<CFGCalendar> wCalendarList, Map<Integer, APSTaskPart> wMaualTaskPartMap, int wLimitMinutes) {

        // 根据前面订单排程结果获取需要排的工位占用时间段 wResult
        List<APSTaskPart> wStationTimeList = wStationAllTimeList.stream()
                .filter(p -> p.RoutePartID == wTaskPart.RoutePartID && p.OrderID != wTaskPart.OrderID)
                .collect(Collectors.toList());

        Calendar wBaseTime = Calendar.getInstance();
        wBaseTime.set(2000, 1, 1);

        // Double wWorkMinus = wManuCapacity.WorkHour;
        // Double wTechMinus = wManuCapacity.Period;

        /*
         * if (wTaskPart.WorkHour > 0) { wWorkMinus -= wTaskPart.WorkHour;
         *
         * if (wTaskPart.PartNo == "") { System.out.println(""); } if(wWorkMinus<=0) {
         * wTechMinus-= }
         *
         * }
         */

        if (wShiftPeriod == APSShiftPeriod.Week) {

        }

        // region ==根据其加工能力判断获取大于此开始时间的工位空闲加工周期 并修改开始时间和结束时间

        // step3 == 根据工位占用详情 获取其在最小开始时间后的空闲时间段
        Map<Calendar, Calendar> wFreeTimeInterval = APSWorkTimeCalcUtils.getInstance()
                .CalcFreeTimeInterval(wStationTimeList, wManuCapacity.getFQTY(), wStartime);

        // 由于设置了100 \\年时间为结束区间 所以不可能没有空闲时间匹配

//		if (wTaskPart.PartID == 110) {
//			System.out.println();
//		}

        // step4 == 循环遍历空闲时间段 并检查时间段是否满足加工工时和工艺工时需求
        for (Calendar wFreeTimeS : wFreeTimeInterval.keySet()) {
            Calendar wFreeTimeE = wFreeTimeInterval.get(wFreeTimeS);

//			if (wTaskPart.getPartID() > 0 && wFreeTimeS.compareTo(wStartime) > 0) {
//				wWorkMinus = (long) (wManuCapacity.WorkHour * 60);
//				wTechMinus = (long) (wManuCapacity.Period * 60);
//			}
            Calendar wTaskEndTime = APSWorkTimeCalcUtils.getInstance().CalcTaskEndTime(wLoginUser, wTaskPart.RoutePartID,
                    wFreeTimeS, wManuCapacity.WorkHour, wManuCapacity.Period, wWorkDay, wAllZoneList, wCalendarList, 0);

            if (wTaskEndTime.compareTo(wFreeTimeE) <= 0) {
                wTaskPart.PartStartTime = wFreeTimeS;
                wTaskPart.PartEndTime = (Calendar) wTaskEndTime.clone();
                break;
            }
        }

        // endRegion

        if (wIsMergeNext) {
            // 获取NextList
            List<APSTaskPart> wNextList = new ArrayList<>();//  GetNextTaskPartList(wBuilderTaskList, wTaskPart.PartID, wRoutePartList);
            for (APSTaskPart apsTaskPart : wNextList) {
                APSManuCapacity manuCapacity = null;
                if (wManuCapacityMap.containsKey(apsTaskPart.RoutePartID))
                    manuCapacity = wManuCapacityMap.get(apsTaskPart.RoutePartID);
                else
                    manuCapacity = new APSManuCapacity();
                if (wMaualTaskPartMap.containsKey(apsTaskPart.RoutePartID))
                    continue;
                EditTaskTime(wLoginUser, apsTaskPart, wShiftPeriod, wRoutePartList, wOutsourceList,
                        (Calendar) wTaskPart.PartEndTime.clone(), wBuilderTaskList, wStationAllTimeList, manuCapacity,
                        wOrderPartIssuedList, wManuCapacityMap, wWorkDay,
                        wIsMergeNext, wAPSMessageList, wAllZoneList, wCalendarList, wMaualTaskPartMap, wLimitMinutes);
            }

        }

    }


}
